﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Security.AccessControl;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;

namespace Conusic.Model.Audio.Server
{
    /// <summary>
    /// Http audio server for produce reading mp3 files into media player
    /// Based on http://www.rsdn.ru/forum/src/322274.1.aspx
    /// </summary>
    public class Interaction
    {
        private readonly TcpClient client;
        private Dictionary<string, string> requestGetParameters;
        private string url;
        /// <summary>
        /// http://tools.ietf.org/html/rfc3986#appendix-B $5 - path
        /// </summary>
        private string path;
        private StreamWriter networkStream;
        private string requestHeaders;

        /// <summary>
        /// New iteraction with audio server constructor
        /// </summary>
        /// <param name="client">Connected Tcp Client</param>
        public Interaction(TcpClient client)
        {
            this.client = client;
            var interact = new Thread(Interact);
            interact.Name = "Http response";
            // set non-blocking thread
            interact.IsBackground = true;
            interact.Start();
        }


        private void Interact()
        {
            try
            {
                byte[] buffer = new byte[1024];
                // Maximal header size 8 kb
                int capacity = buffer.Length * 8;
                StringBuilder requestStr = new StringBuilder(capacity);
                using (var stream = client.GetStream())
                {
                    var continueReading = true;
                    while (continueReading)
                    {
                        int count = stream.Read(buffer, 0, 1024);
                        requestStr.Append(Encoding.ASCII.GetString(buffer, 0, count));
                        if (requestStr.ToString().IndexOf("\r\n\r\n") >= 0 || requestStr.Length >= capacity)
                        {
                            continueReading = false;
                        }
                    }
                    requestHeaders = requestStr.ToString();
                    ParseUrl();
                    ParseParams();

                    networkStream = new StreamWriter(stream);
                    Reply();
                    networkStream.Flush();
                }
                if (client != null) client.Close();
            }
            catch (Exception e)
            {
#if DEBUG
                Debug.WriteLine(e.ToString());
#endif
            }
        }

        private void Reply()
        {
            ServerHeader header;
            switch (requestGetParameters.ContainsKey("act") ?
            requestGetParameters["act"] : string.Empty)
            {
                case "getaudio":
                    var so = VkontakteAudio.SearchOneAudio(requestGetParameters["request"]);
                    if (so != null)
                    {
                        header = new ServerHeader(ServerHeaderType.Redirect, so.Url);
                        networkStream.Write(header);
                        return;
                    }

                    // not found song - not display message for handle in WMP
                    ReportNotFound();
                    break;

                case "saveaudio":
                    var song = VkontakteAudio.SearchOneAudio(requestGetParameters["request"]);
                    if (song != null)
                    {
                        header = new ServerHeader(ServerHeaderType.Ok);
                        Match match = Regex.Match(requestHeaders, "Content-Length: ([0-9]+)");
                        if (match.Success)
                        {
                            header.SetHeader("Content-Length", match.Groups[1].ToString());
                        }
                        header.SetHeader("Content-Type", "audio/mpeg");
                        networkStream.Write(header);
                        networkStream.Flush();

                        string songFilename = song.FullFilename;
                        var download = new DownloadFile(song.Url, songFilename);
                        download.OnProgressFile +=
                            f => networkStream.BaseStream.Write(f.Owner.Buffer, 0, f.Owner.ReadedBytes);
                        download.Download();
                        return;
                    }

                    // not found song - not display message for handle in WMP
                    ReportNotFound();
                    break;

                case "search":
                    // security xss issue
                    var vkSearchResult = VkontakteAudio.SearchAudio(requestGetParameters["request"]);
                    networkStream.Write(new ServerHeader());
                    networkStream.Write(VkontakteAudio.AudioListToJson(vkSearchResult));
                    break;

                case "autocomplete":
                    // security xss issue
                    var lfmResult = LastFm.RequestHints(requestGetParameters["request"]);
                    networkStream.Write(new ServerHeader());
                    networkStream.Write(LastFm.HintListToJson(lfmResult));
                    break;

                default:
                    header = new ServerHeader(ServerHeaderType.Ok);
                    if (String.IsNullOrEmpty(path) || path.Equals("/"))
                        path = "index.html";
                    var filename = String.Format("http{0}{1}", Path.DirectorySeparatorChar, path);
#if DEBUG
                    filename = "c:\\http\\" + path;
#endif

                    if (!File.Exists(filename))
                    {
                        header.headerType = ServerHeaderType.NotFound;
                        networkStream.Write(header);
                        const string notFoundMessage = "404 Not Found";
                        header.SetHeader("Content-Length", notFoundMessage.Length.ToString());
                        networkStream.Write(notFoundMessage);
                        return;
                    }
                    header.SetHeader("Content-Length",
                        (new FileInfo(filename)).Length.ToString());
                    header.SetHeader("Content-Type",
                        ContentType.GetContentType(Path.GetExtension(filename)));

                    networkStream.Write(header);
                    networkStream.Flush();
                    CopyFileToStream(filename, networkStream.BaseStream);

                    return;
            }
        }

        private static void CopyFileToStream(string input, Stream output)
        {
            using (var filestream = new FileStream(input, FileMode.Open, FileAccess.Read))
            {
                byte[] buffer = new byte[32768];
                int read;
                while ((read = filestream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    output.Write(buffer, 0, read);
                }
            }
        }


        private void ReportNotFound()
        {
            var headerFail = new ServerHeader(ServerHeaderType.NotFound);
            networkStream.Write(headerFail);
        }

        ~Interaction()
        {
            if (client != null)
                client.Close();
        }

        private void ParseUrl()
        {
            var match = Regex.Match(requestHeaders, "^GET ([^ ]*) HTTP");
            if (match.Success)
                url = match.Groups[1].ToString();
        }

        private void ParseParams()
        {
            requestGetParameters = new Dictionary<string, string>();
            if (url != null)
            {
                // http://tools.ietf.org/html/rfc3986#appendix-B
                var match = Regex.Match(url, @"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?");
                string query = match.Groups[7].ToString();
                path = match.Groups[5].ToString();
                var arra = query.Split(new[] { '&', '=' });
                for (int i = 0; i < arra.Length - 1; i += 2)
                {
                    var value = HttpUtility.UrlDecode(arra[i + 1], Encoding.UTF8);
                    if (string.IsNullOrEmpty(value))
                        continue;
                    requestGetParameters[arra[i]] = value;
                }
            }
        }
    }
}